JNI 编程上手指南之数组访问

7/3/2023

# 1. 引子

JNI 中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是 JNI的基本数据类型,可以直接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问 Java 传递给 JNI 层的数组,必须选择合适的 JNI 函数来访问和设置 Java 层的数组对象。

# 2. 数组访问示例

# 2.1 基本类型数组

Java 层:

private native double[] sumAndAverage(int[] numbers);
1

JNI 层:

JNIEXPORT jdoubleArray JNICALL Java_HelloJNI_sumAndAverage(JNIEnv *env, jobject obj, jintArray inJNIArray) {
    //类型转换 jintArray -> jint*
    jboolean isCopy;
    jint* inArray = env->GetIntArrayElements(inJNIArray, &isCopy);

    if (JNI_TRUE == isCopy) {
        cout << "C 层的数组是 java 层数组的一份拷贝" << endl;
    } else {
        cout << "C 层的数组指向 java 层的数组" << endl;
    }

    if(nullptr == inArray) return nullptr;
    //获取到数组长度
    jsize length = env->GetArrayLength(inJNIArray);

    jint sum = 0;
    for(int i = 0; i < length; ++i) {
        sum += inArray[i];
    }

    jdouble average = (jdouble)sum / length;
    //释放数组
    env->ReleaseIntArrayElements(inJNIArray, inArray, 0); // release resource

    //构造返回数据,outArray 是指针类型,需要 free 或者 delete 吗?要的
    jdouble outArray[] = {sum, average};
    jdoubleArray outJNIArray = env->NewDoubleArray(2);
    if(NULL == outJNIArray) return NULL;
    //向 jdoubleArray 写入数据
    env->SetDoubleArrayRegion(outJNIArray, 0, 2, outArray);
    return outJNIArray;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 2.2 引用类型数组

Java 层:

public native String[] operateStringArrray(String[] array);
1

JNI 层:

JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateStringArrray
  (JNIEnv * env, jobject object, jobjectArray objectArray_in)
{
    //获取到长度信息
    jsize  size = env->GetArrayLength(objectArray_in);

	/*******获取从JNI传过来的String数组数据**********/


	for(int i = 0; i < size; i++)
	{
		jstring string_in= (jstring)env->GetObjectArrayElement(objectArray_in, i);
        char *char_in  = env->GetStringUTFChars(str, nullptr);
	}


	/***********从JNI返回String数组给Java层**************/
	jclass clazz = env->FindClass("java/lang/String");
	jobjectArray objectArray_out;
	const int len_out = 5;
	objectArray_out = env->NewObjectArray(len_out, clazz, NULL);
	char * char_out[]=  { "Hello,", "world!", "JNI", "is", "fun" };

	jstring temp_string;
	for( int i= 0; i < len_out; i++ )
    {   
        temp_string = env->NewStringUTF(char_out[i]);
        env->SetObjectArrayElement(objectArray_out, i, temp_string);
    }
	return objectArray_out;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 2.3 二维数组

Java 层:

public native int[][] operateTwoIntDimArray(int[][] array_in);
1

JNI 层:

JNIEXPORT jobjectArray JNICALL Java_com_xxx_jni_JNIArrayManager_operateTwoIntDimArray(JNIEnv * env, jobject object, jobjectArray objectArray_in)
{
	/**********	解析从Java得到的int型二维数组 **********/
	int i, j ;
	const int row = env->GetArrayLength(objectArray_in);//获取二维数组的行数
	jarray array = (jarray)env->GetObjectArrayElement(objectArray_in, 0);
	const int col = env->GetArrayLength(array);//获取二维数组每行的列数

	//根据行数和列数创建int型二维数组
	jint intDimArrayIn[row][col];

	
	for(i =0; i < row; i++)
	{
		 array = (jintArray)env->GetObjectArrayElement(objectArray_in, i);
		
		 //操作方式一,这种方法会申请natvie memory内存
		 jint *coldata = env->GetIntArrayElements((jintArray)array, NULL );        
         for (j=0; j<col; j++) {    
              intDimArrayIn [i] [j] = coldata[j]; //取出JAVA类中int二维数组的数据,并赋值给JNI中的数组  
         }  

		  //操作方式二,赋值,这种方法不会申请内存
  		//  env->GetIntArrayRegion((jintArray)array, 0, col, (jint*)&intDimArrayIn[i]);		 
		  
		 env->ReleaseIntArrayElements((jintArray)array, coldata,0 );  
	}

	/**************创建一个int型二维数组返回给Java**************/
	const int row_out = 2;//行数
	const int col_out = 2;//列数

	//获取数组的class
	jclass clazz  = env->FindClass("[I");//一维数组的类
	//新建object数组,里面是int[]
	jobjectArray intDimArrayOut = env->NewObjectArray(row_out, clazz, NULL);

	int tmp_array[row_out][col_out] = {{0,1},{2,3}};
	for(i = 0; i< row_out; i ++)
	{
		jintArray intArray = env->NewIntArray(col_out);
		env->SetIntArrayRegion(intArray, 0, col_out, (jint*)&tmp_array[i]);
		env->SetObjectArrayElement(intDimArrayOut, i, intArray);
	}
	return intDimArrayOut;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 3. JNI 字符串处理函数

GetArrayLength

jsize (GetArrayLength)(JNIEnv env, jarray array);
1

返回数组中的元素个数

NewObjectArray

jobjectArray NewObjectArray (JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
1

构建 JNI 引用类型的数组,它将保存类 elementClass 中的对象。所有元素初始值均设为 initialElement,一般使用 NULL 就好。如果系统内存不足,则抛出 OutOfMemoryError 异常

GetObjectArrayElement和SetObjectArrayElement

jobject GetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index)
1

返回 jobjectArray 数组的元素,通常是获取 JNI 引用类型数组元素。如果 index 不是数组中的有效下标,则抛出 ArrayIndexOutOfBoundsException 异常。

void SetObjectArrayElement (JNIEnv *env, jobjectArray array, jsize index, jobject value)
1

设置 jobjectArray 数组中 index 下标对象的值。如果 index 不是数组中的有效下标,则会抛出 ArrayIndexOutOfBoundsException 异常。如果 value 的类不是数组元素类的子类,则抛出 ArrayStoreException 异常。

New<PrimitiveType>Array 函数集

NativeTypeArray New<PrimitiveType>Array (JNIEnv* env, jsize size)
1

用于构造 JNI 基本类型数组对象。

在实际应用中把 PrimitiveType 替换为某个实际的基本类型数据类型,然后再将 NativeType 替换成对应的 JNI Native Type 即可,具体的:

函数名                      返回类型
NewBooleanArray()           jbooleanArray
NewByteArray()              jbyteArray
NewCharArray()              jcharArray
NewShortArray()             jshorArray
NewIntArray()               jintArray
NewLongArray()              jlongArray
NewFloatArray()             jfloatArray
NewDoubleArray()            jdoubleArray      
1
2
3
4
5
6
7
8
9

Get/ReleaseArrayElements函数集

NativeType* Get<PrimitiveType>ArrayElements(JNIEnv *env, NativeTypeArray array, jboolean *isCopy)
1

该函数用于将 JNI 数组类型转换为 JNI 基本数据类型数组,在实际使用过程中将 PrimitiveType 替换成某个实际的基本类型元素访问函数,然后再将NativeType替换成对应的 JNI Native Type 即可:

函数名                           转换前类型             转换后类型
GetBooleanArrayElements()       jbooleanArray          jboolean*
GetByteArrayElements()          jbyteArray             jbyte*
GetCharArrayElements()          jcharArray             jchar*
GetShortArrayElements()         jshortArray            jshort*
GetIntArrayElements()           jintArray              jint*
GetLongArrayElements()          jlongArray             jlong*
GetFloatArrayElements()         jfloatArray            jfloat*
GetDoubleArrayElements()        jdoubleArray           jdouble*
1
2
3
4
5
6
7
8
9
void Release<PrimitiveType>ArrayElements (JNIEnv *env, NativeTypeArray array, NativeType *elems,jint mode);
1

该函数用于通知 JVM,数组不再使用,可以清理先关内存了。在实际使用过程中将 PrimitiveType 替换成某个实际的基本类型元素访问函数,然后再将 NativeType 替换成对应的 JNI Native Type 即可:

函数名                              NativeTypeArray        NativeType
ReleaseBooleanArrayElements()       jbooleanArray          jboolean
ReleaseByteArrayElements()          jbyteArray             jbyte
ReleaseCharArrayElements()          jcharArray             jchar
ReleaseShortArrayElements()         jshortArray            jshort
ReleaseIntArrayElements()           jintArray              jint
ReleaseLongArrayElements()          jlongArray             jlong
ReleaseFloatArrayElements()         jfloatArray            jfloat
ReleaseDoubleArrayElements()        jdoubleArray           jdouble
1
2
3
4
5
6
7
8
9

Get/Set<PrimitiveType>ArrayRegion

void Set<PrimitiveType>ArrayRegion (JNIEnv *env, NativeTypeArray array, jsize start, jsize len, NativeType *buf);
1

该函数用于将基本类型数组某一区域复制到 JNI 数组类型中。在实际使用过程中将 PrimitiveType 替换成某个实际的基本类型元素访问函数,然后再将 NativeType 替换成对应的 JNI Native Type 即可:

函数名                              NativeTypeArray        NativeType
SetBooleanArrayRegion()             jbooleanArray          jboolean
SetByteArrayRegion()                jbyteArray             jbyte
SetCharArrayRegion()                jcharArray             jchar
SetShortArrayRegion()               jshortArray            jshort
SetIntArrayRegion()                 jintArray              jint
SetLongArrayRegion()                jlongArray             jlong
SetFloatArrayRegion()               jfloatArray            jfloat
SetDoubleArrayRegion()              jdoubleArray           jdouble
1
2
3
4
5
6
7
8
9